package org.jeecg.modules.oauth.xkwoauth.controller;

import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.common.baseentity.BaseApiEntity;
import org.jeecg.modules.common.baseservice.SysInfo;
import org.jeecg.modules.oauth.xkwoauth.entity.SysThirdOauthRelationship;
import org.jeecg.modules.oauth.xkwoauth.service.ISysThirdOauthRelationshipService;
import org.jeecg.modules.oauth.xkwoauth.utils.AesUtils;
import org.jeecg.modules.oauth.xkwoauth.utils.ConfigUtils;
import org.jeecg.modules.oauth.xkwoauth.utils.HttpsUtils;
import org.jeecg.modules.oauth.xkwoauth.utils.SignatureUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;

/**
 * @Author: lwz
 * @Description: 学科网认证登录执行类
 * @date: 11:49 2018/1/10
 */
@RestController
@RequestMapping("/oauth/xkwoauth/oauthClientController")
@Api(tags="学科网认证登录执行类")
@Slf4j
public class OauthClientController {

    @Autowired
    private ISysThirdOauthRelationshipService sysThirdOauthRelationshipService;
    /**
     * 学科网分配的appkey
     */
    private final String appKey;
    /**
     * 学科网分配的appSecret
     */
    private final String appSecret;
    /**
     * 学科网的服务，如http://www.zxxk.com/、http://zujuan.xkw.com/等，注意域名后面的斜杠不能少，更多服务可联系客服获取
     */
    private final String service;
    /**
     * 学科网Oauth认证平台地址
     */
    private final String oauthServerUrl;
    @Autowired
    private SysInfo sysInfo;
    @Autowired
    private ISysBaseAPI sysBaseAPI;

    @Autowired
    public OauthClientController(ConfigUtils configUtils) {
        appKey = configUtils.getAppKey();
        appSecret = configUtils.getAppSecret();
        service = configUtils.getService();
        oauthServerUrl = configUtils.getOauthServerUrl();
    }

    /**
     * 平台认证执行器
     */
    @RequestMapping(value = "/authentication", method = {RequestMethod.POST, RequestMethod.GET})
    public Result<?> processRequest(HttpServletRequest request, Model model, HttpSession session) throws Exception {
        List result;
        //POST:(@RequestBody Object requestParm,HttpServletRequest req)
        //Map requestParmMap = JSONObject.parseObject(JSONObject.toJSONString(requestParm), Map.class);
        //获取当前登录用户信息
        String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
        Map requestParmMap = sysInfo.getMapQueryParamByReq(request);
        if(oConvertUtils.isEmpty(token)){
            token = request.getParameter("smartschoolToken");
        }
        String username = JwtUtil.getUsername(token);

        BaseApiEntity baseApiEntity = sysInfo.getBaseApiEntity(request);
        LoginUser loginUser ;//学科网回调的时候，无法从request获得token,也无法获得baseApiEntity和loginUser
        if(oConvertUtils.isNotEmpty(baseApiEntity)){
            loginUser = baseApiEntity.getLoginUser();
        }
        else{
            loginUser = sysBaseAPI.getUserByName(username);
        }

        //从缓存中获取userId，如果不存在说明用户未登录第三方应用，强制跳转第三方登录页面
//        User user = (User) session.getAttribute("userInfo");
//        User user= new User();//session.getAttribute("userInfo")==null?null:(User)session.getAttribute("userInfo");
//        user.setUserId(loginUser.getId());
//        user.setUserName(loginUser.getUsername());


        if (oConvertUtils.isEmpty(loginUser)) {
            return Result.OK("请先登录系统！");//"login";
        }
        //从http请求中获取code
        String code = request.getParameter("code");
        //动态获取本地的回调地址
        final String redirectUri = getRedirectUrl(request);
        String openId = "";
        //code为空,跳转学科网Oauth授权中心获取code
        if (oConvertUtils.isEmpty(code)) {
            //根据userId获取openId
            SysThirdOauthRelationship sysThirdOauthRelationship = sysThirdOauthRelationshipService.getByUserId(loginUser.getId());
            if (oConvertUtils.isNotEmpty(sysThirdOauthRelationship) && oConvertUtils.isNotEmpty(sysThirdOauthRelationship.getThirdOpenid()) &&  !"0".equals(openId)) {
                openId = sysThirdOauthRelationship.getThirdOpenid();
            }
            result = new ArrayList();
            Map redirectMap = new HashMap();
            redirectMap.put("redirect",getAuthorizeUrl(openId, redirectUri));
            result.add(redirectMap);
            return  Result.OK(redirectMap);//"redirect:" + getAuthorizeUrl(openId, redirectUri);
        }
        //获取accessToken
        String accessTokenResp = getAccessToken(code, redirectUri);
        String accessToken = getAccessTokenFromResp(accessTokenResp);
        if (oConvertUtils.isEmpty(accessToken)) {
            model.addAttribute("message", "认证失败，" + getErrorFromResp(accessTokenResp));
            return  Result.OK("认证失败，" + getErrorFromResp(accessTokenResp));//"failure";
        }
        //获取openId
        String profileResp = getProfile(accessToken);
        openId = getOpenIdFromResp(profileResp);
        if (oConvertUtils.isEmpty(openId) || "0".equals(openId)) {
            model.addAttribute("message", "认证失败，" + getErrorFromResp(profileResp));
            return  Result.OK("认证失败，" + getErrorFromResp(accessTokenResp));//"failure";
        }

        /*
         * 解析xkw返回的新的openId，然后查询该用户是否已经绑定openId。如果未绑定，则添加相应的关联关系；
         * 如果已绑定，则更新最新的openId、addTime（维护用户和openId的关联关系）
         */
//        boolean res = maintainUserSysThirdOauthRelationship(loginUser.getId(), openId);
//        SysThirdOauthRelationship sysThirdOauthRelationship = sysThirdOauthRelationshipService.getByOpenId(openId);
        SysThirdOauthRelationship sysThirdOauthRelationship = sysThirdOauthRelationshipService.getByUserId(loginUser.getId());
        if(oConvertUtils.isEmpty(sysThirdOauthRelationship)){
            sysThirdOauthRelationship = new SysThirdOauthRelationship();
            sysThirdOauthRelationship.setSysUserId(loginUser.getId());
//            sysThirdOauthRelationship.setThirdOpenid(openId);
            sysThirdOauthRelationship.setTenantId(loginUser.getTenantId());
        }
        sysThirdOauthRelationship.setThirdOpenid(openId);
        sysThirdOauthRelationship.setThirdType("xkw");
        sysThirdOauthRelationship.setTenantId(loginUser.getTenantId());
        sysThirdOauthRelationship.setThirdUserId(openId);
        sysThirdOauthRelationship.setThirdUserName(loginUser.getRealname());
        sysThirdOauthRelationship.setIzDelete(0);
        sysThirdOauthRelationship.setIzOpen(1);
        boolean res = sysThirdOauthRelationshipService.saveOrUpdate(sysThirdOauthRelationship);
        if (res) {
            //用户关联关系正确维护，跳转指定服务地址
            String urlToRedirect = String.format(oauthServerUrl + "/login?service=%s", service);
            result = new ArrayList();
            Map redirectMap = new HashMap();
            redirectMap.put("redirect",urlToRedirect);
            result.add(redirectMap);
            return  Result.OK(redirectMap);//"redirect:" + urlToRedirect;
        }
        //未维护成功，跳转错的地址
        model.addAttribute("message", "认证成功，更新用户关联关系失败");
        return Result.OK("failure"+"认证成功，更新用户关联关系失败");//"failure";
    }

    /**
     * 获取Oauth授权地址
     *
     * @param openId      学科网返回给应用的OpenId
     * @param redirectUri 本系统的回调地址，用来处理Oauth接下来的逻辑
     * @return 授权地址
     */
    private String getAuthorizeUrl(String openId, String redirectUri) throws Exception {
        return getAuthorizeUrl(openId, redirectUri, null);
    }

    /**
     * 获取Oauth授权地址，除非对接时学科网有特别说明，否则不要使用该方法，您可以使用{@link OauthClientController#getAuthorizeUrl(String, String)}
     *
     * @param openId      学科网返回给应用的OpenId，非空
     * @param extra       填写手机号，邮箱，用户名中的任意一项，推荐填写顺序从高到低，对接时学科网会特别说明，非空
     * @param redirectUri 本系统的回调地址，用来处理Oauth接下来的逻辑
     * @return 授权地址
     */
    private String getAuthorizeUrl(String openId, String redirectUri, String extra) throws Exception {
        openId = AesUtils.aesEncrypt(openId, appSecret);
        String timeSpan = AesUtils.aesEncrypt(getTimeStamp(), appSecret);
        String url = oauthServerUrl + "/oauth2/authorize";
        //此处必须使用SortedMap，主要是利用SortedMap能够根据参数名首字母自动进行排序的特性
        SortedMap<String, Object> paramMap = new TreeMap<>();
        paramMap.put("client_id", appKey);
        paramMap.put("open_id", openId);
        paramMap.put("service", service);
        paramMap.put("redirect_uri", redirectUri);
        paramMap.put("timespan", timeSpan);
        paramMap.put("extra", StringUtils.isEmpty(extra) ? "" : extra);
        paramMap.put("signature", SignatureUtils.generateSignature(paramMap, appSecret));
        //加密之后参数中会含有特殊字符，如加号“+”，会导致浏览器url解析成空格，因此需要给参数值编一下码
        return url + "?" + getEncodedParamStr(paramMap);
    }

    /**
     * 内部方法：获取accessToken认证
     *
     * @param code        非空
     * @param redirectUri 回调地址，非空
     * @return 获取到的json字符串，内包含accessToken信息
     */
    private String getAccessToken(String code, String redirectUri) throws Exception {
        //此处必须使用SortedMap，主要是利用SortedMap能够根据参数名首字母自动进行排序的特性
        SortedMap<String, Object> paramMap = new TreeMap<>();
        paramMap.put("client_id", appKey);
        paramMap.put("code", code);
        paramMap.put("redirect_uri", redirectUri);
        paramMap.put("signature", SignatureUtils.generateSignature(paramMap, appSecret));
        String url = oauthServerUrl + "/oauth2/accessToken?" + getEncodedParamStr(paramMap);
        return HttpsUtils.httpPost(url);
    }

    /**
     * 内部方法：获取OpenId
     *
     * @param accessToken 非空
     * @return 获取到的json字符串，内包含OpenId信息
     */
    private String getProfile(String accessToken) throws Exception {
        String url = String.format(oauthServerUrl + "/oauth2/profile?access_token=%s", accessToken);
        return HttpsUtils.httpPost(url);
    }

    /**
     * 内部方法：获取OpenId，除非对接时学科网有特别说明，否则不要使用该方法，您可使用{@link OauthClientController#getProfile(String)}
     *
     * @param accessToken 非空
     * @param schoolId    由学科网分配，对接时学科网会特别说明该参数，非空
     * @return 获取到的json字符串，内包含OpenId信息
     */
    private String getProfile(String accessToken, Integer schoolId) throws Exception {
        String url = String.format(oauthServerUrl + "/oauth2/profile?access_token=%s&schoolId=%s", accessToken, schoolId);
        return HttpsUtils.httpPost(url);
    }

    /**
     * 获取accessToken
     *
     * @param strResp respone响应字符串
     */
    private String getAccessTokenFromResp(String strResp) {
        return getFiledFormResp(strResp, "access_token");
    }

    /**
     * 获取openId
     *
     * @param strResp respone响应字符串
     */
    private String getOpenIdFromResp(String strResp) {
        return getFiledFormResp(strResp, "open_id");
    }

    /**
     * 获取error信息
     *
     * @param strResp respone响应字符串
     */
    private String getErrorFromResp(String strResp) {
        return getFiledFormResp(strResp, "error");
    }

    /**
     * 内部方法：从xkw返回数据中解析指定字段的数据值
     *
     * @param strResp respone响应字符串
     * @param filed   指定字段
     * @return 指定字段的值
     */
    private String getFiledFormResp(String strResp, String filed) {
        if (StringUtils.isEmpty(strResp) || StringUtils.isEmpty(filed)) {
            return "";
        }
        //将json数据转为map格式进行维护
        JSONObject jasonObject = JSONObject.parseObject(strResp);
        return (String) jasonObject.get(filed);
    }

    /**
     * 维护openId与userId的关联关系;在存储对应关系时，第三方应用需判断当前用户是否已经绑定openId，
     * 如果已经绑定则进行更新，如果未绑定则进行插入；
     */
//    private boolean maintainUserSysThirdOauthRelationship(String userId, String openId) {
//        if (oConvertUtils.isEmpty(openId) || oConvertUtils.isEmpty(userId)) {
//            return false;
//        }
//        SysThirdOauthRelationship SysThirdOauthRelationship = SysThirdOauthRelationshipService.getByUserId(userId);
//        if (SysThirdOauthRelationship == null) {
//            //插入用户关联关系
//            return SysThirdOauthRelationshipService.addUserSysThirdOauthRelationship(userId, openId, new Date());
//        }
//        //更新用户关联关系
//        return SysThirdOauthRelationshipService.updateUserSysThirdOauthRelationship(userId, openId, new Date());
//    }

    /**
     * 内部方法：动态获取项目的回调地址
     */
    private  String getRedirectUrl(HttpServletRequest request) {
        String url = "http://smartschool.lczhjy.com/smart_school/oauth/xkwoauth/xkwoauth";//"http://smartschool.lczhjy.com/smart_school/oauth/xkwoauth/oauthClientController/authentication";//"http://smartschool.lczhjy.com/";///  "http://smartschool.lczhjy.com/oauth/xkwoauth/oauthClientController/authentication"
        String urlScheme = request.getScheme();
        String relServerName = "smartschool-boot.lczhjy.com";//request.getServerName();
        int urlServerPort = 80;//request.getServerPort();
        String urlContextPath = request.getContextPath();
        //获取当前登录用户信息
        String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
        Map requestParmMap = sysInfo.getMapQueryParamByReq(request);
        if(oConvertUtils.isEmpty(token)){
            token = request.getParameter("smartschoolToken");
        }
        if (urlServerPort == 80) {
            url = String.format("%s://%s%s/oauth/xkwoauth/oauthClientController/authentication", urlScheme, relServerName, urlContextPath);
            url = url + "?smartschoolToken="+token;
            return url;
        }
        url = String.format("%s://%s:%s%s/oauth/xkwoauth/oauthClientController/authentication", urlScheme, relServerName, urlServerPort, urlContextPath);

        url = url + "?smartschoolToken="+token;
        return url;
    }

    /**
     * 内部方法：获取时间戳
     */
    private String getTimeStamp() {
        return String.valueOf(System.currentTimeMillis());
    }

    /**
     * 将所有的参数值进行编码，防止参数值中会参数特殊字符，如加号"+"，导致浏览器url解析成空格，因此需要给参数值编一下码
     *
     * @param paramMap 参数Map
     * @return
     * @throws UnsupportedEncodingException
     */
    private String getEncodedParamStr(Map<String, Object> paramMap) throws UnsupportedEncodingException {
        StringBuilder encodedParamStr = new StringBuilder();
        for (Map.Entry param : paramMap.entrySet()) {
            if (encodedParamStr.length() > 0) {
                encodedParamStr.append("&");
            }
            encodedParamStr.append(param.getKey()).append("=").append(URLEncoder.encode(param.getValue().toString(), "utf-8"));
        }
        return encodedParamStr.toString();
    }
}
